using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MalwareMultiScan.Api.Data.Models;
using MalwareMultiScan.Api.Services.Interfaces;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;

namespace MalwareMultiScan.Api.Services.Implementations
{
    /// <inheritdoc />
    public class ScanResultService : IScanResultService
    {
        private const string CollectionName = "ScanResults";

        private readonly IGridFSBucket _bucket;
        private readonly IMongoCollection<ScanResult> _collection;
        private readonly IScanBackendService _scanBackendService;

        /// <summary>
        /// Initialize scan result service.
        /// </summary>
        /// <param name="db">Mongo database.</param>
        /// <param name="bucket">GridFS bucket.</param>
        /// <param name="scanBackendService">Scan backend service.</param>
        public ScanResultService(IMongoDatabase db, IGridFSBucket bucket, IScanBackendService scanBackendService)
        {
            _bucket = bucket;
            _scanBackendService = scanBackendService;

            _collection = db.GetCollection<ScanResult>(CollectionName);
        }
        
        /// <inheritdoc />
        public async Task<ScanResult> CreateScanResult()
        {
            var scanResult = new ScanResult
            {
                Results = _scanBackendService.List
                    .Where(b => b.Enabled)
                    .ToDictionary(k => k.Id, v => new ScanResultEntry())
            };

            await _collection.InsertOneAsync(scanResult);

            return scanResult;
        }

        /// <inheritdoc />
        public async Task<ScanResult> GetScanResult(string id)
        {
            var result = await _collection.FindAsync(
                Builders<ScanResult>.Filter.Where(r => r.Id == id));

            return await result.FirstOrDefaultAsync();
        }

        /// <inheritdoc />
        public async Task UpdateScanResultForBackend(string resultId, string backendId, long duration,
            bool completed = false, bool succeeded = false, string[] threats = null)
        {
            await _collection.UpdateOneAsync(
                Builders<ScanResult>.Filter.Where(r => r.Id == resultId),
                Builders<ScanResult>.Update.Set(r => r.Results[backendId], new ScanResultEntry
                {
                    Completed = completed,
                    Succeeded = succeeded,
                    Duration = duration,
                    Threats = threats ?? new string[] { }
                }));
        }

        /// <inheritdoc />
        public async Task QueueUrlScan(ScanResult result, string fileUrl)
        {
            foreach (var backend in _scanBackendService.List.Where(b => b.Enabled))
                await _scanBackendService.QueueUrlScan(result, backend, fileUrl);
        }

        /// <inheritdoc />
        public async Task<string> StoreFile(string fileName, Stream fileStream)
        {
            var objectId = await _bucket.UploadFromStreamAsync(
                fileName, fileStream);

            return objectId.ToString();
        }

        /// <inheritdoc />
        public async Task<Stream> ObtainFile(string id)
        {
            if (!ObjectId.TryParse(id, out var objectId))
                return null;

            try
            {
                return await _bucket.OpenDownloadStreamAsync(objectId, new GridFSDownloadOptions
                {
                    Seekable = true
                });
            }
            catch (GridFSFileNotFoundException)
            {
                return null;
            }
        }
    }
}